﻿/**
 * Authors: Tom & Mikel Byrne 2007
 */

package org.tbyrne.motion{
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.TimerEvent;
	import flash.utils.Timer;
	import flash.utils.getTimer;
	
	[Event(type="org.tbyrne.motion.AtomicMotionEvent",name="valueChange")]
	[Event(type="org.tbyrne.motion.AtomicMotionEvent",name="motionChange")]
	[Event(type="org.tbyrne.motion.AtomicMotionEvent",name="motionBegin")]
	[Event(type="org.tbyrne.motion.AtomicMotionEvent",name="motionEnd")]
	public class AtomicMotion extends EventDispatcher{
		private static var frameHook:Sprite;
		{
			frameHook = new Sprite();
		}
		
		public static var immediateCalculation:Boolean = false;
		
		private static const SMALL_NUMBER:Number = 0.000001;
		
		
		[Bindable("change")]
		public function get speed():Number{
			preCalculate();
			if(speedInvalid){
				speedInvalid = false;
				var time:Number = (getTimer()-startTime)/1000;
				if(_duration)time = time*(_calcDuration/_duration);
				//_speed =  (Z1*A*Math.pow(Math.E,Z1*time))+(Z2*B*Math.pow(Math.E,Z2*time));
				_speed = ((_initialSpeed*Math.cos(H*time))-(L*Math.sin(H*time)))*Math.pow(Math.E,(-_decceleration/mass)*time);
				
				_speed = isNaN(_speed)?0:_speed;
			}
			return _speed;
		}
		public function set speed(to:Number):void{
			startValue = value;
			preCalcInvalid = true;
			_initialSpeed = to;
		}
		
		[Bindable("change")]
		public function get value():Number{
			this.validateValue();
			return _value;
		}
		public function set value(to:Number):void{
			startValue = _value = to;
			if(isNaN(_destination))_destination = to;
			invalidatePreCalc();
		}
		
		public function get duration():Number{
			if(_duration)return _duration;
			else{
				preCalculate();
				return _calcDuration;
			}
		}
		public function set duration(to:Number):void{
			if(_duration != to){
				_duration = to;
				if(!playing)invalidatePreCalc()
				else invalidatePostCalc()
			}
		}
		public function get destination():Number{
			return _destination;
		}
		public function set destination(to:Number):void{
			if(_destination != to){
				_destination = to;
				this.validateValue();
				if(!playing){invalidatePreCalc(); }
				else { invalidatePostCalc();  }
			}
		}
		public function get mass():Number{
			return _mass;
		}
		public function set mass(to:Number):void{
			_mass = to;
			if(!playing)invalidatePreCalc()
			else invalidatePostCalc()
		}
		public function get acceleration():Number{
			return _acceleration;
		}
		public function set acceleration(to:Number):void{
			_acceleration = to;
			if(!playing)invalidatePreCalc()
			else invalidatePostCalc()
		}
		public function get decceleration():Number{
			return _decceleration;
		}
		public function set decceleration(to:Number):void{
			_decceleration = to;
			if(!playing)invalidatePreCalc()
			else invalidatePostCalc()
		}
		public function get interval():Number{
			return _interval;
		}
		public function set interval(to:Number):void{
			_interval = to;
			if(playing)restart();
		}
		public function get rounding():Number{
			return _rounding;
		}
		public function set rounding(to:Number):void{
			_rounding = to;
			if(!playing)invalidatePreCalc()
			else invalidatePostCalc()
		}
		public function get time():Number{
			return getTimer()-startTime;
		}
		
		private var startValue:Number = 0;
		private var startTime:Number = 0;
		private var _initialSpeed:Number = 0;	// v
		private var g:Number = 0;				// acceleration (obsolete)
		private var _acceleration:Number = 0;	// k
		private var _decceleration:Number = 0;	// b
		private var _mass:Number = 0;			// m
		private var _destination:Number;		// x1
		private var _duration:Number;
		private var _calcDuration:Number;
		private var _value:Number = 0;
		private var _speed:Number = 0;
		private var _interval:Number = NaN;
		private var _rounding:Number = 0;		// r
		private var playing:Boolean = false;
		
		private var calcTimer:Timer;
		private var finishTimer:Timer;
		private var preCalcInvalid:Boolean = true;
		private var postCalcInvalid:Boolean = true;
		private var valueInvalid:Boolean = true;
		private var speedInvalid:Boolean = true;
		private var dispatchEvents:Boolean = true;
		
		
		// calculation caches
		private var H:Number;
		private var X2:Number;
		private var J:Number;
		private var L:Number;
		private var N:Number;
		
		public function AtomicMotion(mass:Number = NaN, acceleration:Number = NaN, decceleration:Number = NaN){
			if(!isNaN(mass))this.mass = mass;
			if(!isNaN(acceleration))this.acceleration = acceleration;
			if(!isNaN(decceleration))this.decceleration = decceleration;
		}
		
		private function validateValue(): void
		{
			preCalculate();
			if(valueInvalid){
				valueInvalid = false;
				var time:Number = (getTimer()-startTime)/1000;
				if(_duration)time = time*(_calcDuration/_duration);
				var mass:Number = Math.max(_mass,SMALL_NUMBER);
				var acceleration:Number = Math.max(_acceleration,SMALL_NUMBER);
				//var tempValue:Number = (A*Math.pow(Math.E,Z1*time))+(B*Math.pow(Math.E,Z2*time))+((g*mass)/(2*acceleration))+_destination;
				var tempValue:Number = ((X2*Math.cos(H*time))+(Math.sin(H*time)*J))*Math.pow(Math.E,(-_decceleration/mass)*time)+N;
				
				if(_rounding>0){
					tempValue *= 1/_rounding;
					tempValue = Math.round(tempValue);
					tempValue /= 1/_rounding;
				}
				if(!isNaN(tempValue)){
					_value = tempValue;
				}
			}
		}
		
		private function restart():void{
			stop();
			_start();
			if(dispatchEvents)dispatchEvent(new AtomicMotionEvent(AtomicMotionEvent.MOTION_CHANGE));
		}
		public function start():void{
			if(!playing){
				if(dispatchEvents)dispatchEvent(new AtomicMotionEvent(AtomicMotionEvent.MOTION_BEGIN));
				_start();
			}
		}
		private function _start():void{
			if(!playing){
				playing = true;
				if(!isNaN(_interval)){
					calcTimer = new Timer(_interval);
					calcTimer.start();
					calcTimer.addEventListener(TimerEvent.TIMER,calcTimerTick);
				}else{
					frameHook.addEventListener(Event.ENTER_FRAME,calcTimerTick);
				}
				//startTime = getTimer();
				invalidatePreCalc();
				startTimer();
			}
		}
		private function calcTimerTick(e:Event = null):void{
			speedInvalid = valueInvalid = true;
			if(dispatchEvents)dispatchEvent(new AtomicMotionEvent(AtomicMotionEvent.VALUE_CHANGE));
			if(postCalcInvalid){
				postCalcInvalid = false;
				invalidatePreCalc();
			}
		}
		private function finishTimerTick(e:TimerEvent = null):void{
			if(postCalcInvalid || preCalcInvalid){
				// if destination/duration has change between the last frame and this handler we need to continue
				postCalcInvalid = false;
				invalidatePreCalc();
			}else{
				stop();
				_value = _destination;
				if(dispatchEvents){
					dispatchEvent(new AtomicMotionEvent(AtomicMotionEvent.VALUE_CHANGE));
					dispatchEvent(new AtomicMotionEvent(AtomicMotionEvent.MOTION_END));
				}
			}
		}
		public function stop():void{
			if(playing){
				playing = false;
				if(calcTimer){
					calcTimer.removeEventListener(TimerEvent.TIMER,calcTimerTick);
					calcTimer.stop();
					calcTimer = null;
				}
				frameHook.removeEventListener(Event.ENTER_FRAME,calcTimerTick);
				stopTimer();
				_calcDuration = startTime = NaN;
			}
		}
		private function invalidatePreCalc():void{
			dispatchEvents = false;
			_initialSpeed = speed;
			startValue = value;
			if(playing)startTime = getTimer();
			preCalcInvalid = true;
			postCalcInvalid = false;
			dispatchEvents = true;
			if(immediateCalculation)preCalculate();
		}
		private function invalidatePostCalc():void{
			if(!preCalcInvalid)postCalcInvalid = true;
		}
		private function preCalculate():void{
			if(preCalcInvalid && !isNaN(destination)){
				preCalcInvalid = false;
				valueInvalid = true;
				var mass:Number = Math.max(_mass,SMALL_NUMBER);
				var acceleration:Number = Math.max(_acceleration,SMALL_NUMBER);
				N = ((g*mass)/(2*acceleration))+_destination;
				var initH:Number = (Math.pow(_decceleration,2)/(4*Math.pow(mass,2)))-((2*acceleration)/mass);
				H = Math.sqrt(Math.max(SMALL_NUMBER,Math.abs(initH)));
				
				X2 = startValue-((g*mass)/(2*acceleration))-_destination;
				J = ((_decceleration*X2)/(mass*H))+(_initialSpeed/H);
				L = ((_decceleration/mass)*J)+(X2*H);
				
				//if(initH>0)_calcDuration = (1/H)*Math.atan(-(X2/J))+((2*Math.PI)/H);
				//else{
					var distance:Number = Math.abs(_destination-startValue)
					var round:Number = Math.max(SMALL_NUMBER,_rounding);
					var time1:Number = (-_mass/_decceleration)*Math.log(Math.abs(round/X2));
					var time2:Number = (-_mass/_decceleration)*Math.log(Math.abs(round/(((_decceleration*X2)/(H*_mass))+(_initialSpeed/H))));
					_calcDuration = Math.max(time1,time2,0);
				//}
				if(playing)startTimer();
			}
		}
		private function stopTimer():void{
			if(finishTimer){
				finishTimer.stop();
			}
		}
		private function startTimer():void{
			stopTimer();
			preCalculate(); // make sure duration has been calculated
			if(!isNaN(duration) && !isNaN(startTime)){
				var delay:Number = (duration*1000)-(getTimer()-startTime);
				if(delay>0){
					if(!finishTimer){
						finishTimer = new Timer(delay,1);
						finishTimer.addEventListener(TimerEvent.TIMER,finishTimerTick);
					}
					finishTimer.delay = delay
					finishTimer.reset();
					finishTimer.start();
				}else{
					finishTimerTick();
				}
			}
		}
	}
}
/*
"For every moment of triumph, for every instance of beauty, many souls must be trampled."

"I feel the same way about disco as I do about herpes."

"I wouldn't recommend sex, drugs or insanity for everyone, but they've always worked for me."

"If I'd written all the truth I knew for the past ten years, about 600 people - including me - would be
rotting in prison cells from Rio to Seattle today. Absolute truth is a very rare and dangerous commodity
in the context of professional journalism."

"America... just a nation of two hundred million used car salesmen with all the money we need to buy guns
and no qualms about killing anybody else in the world who tries to make us uncomfortable."

																								Hunter S. Thompson

cc:::c:c:::::c:::c:c::::::::.::::.:::::::::::::::::::::c::c::::.::::..:::::::::::::c::c:::::c:::c::::::::::::::....:::::::::::::::::::::::::::::::::cc
c:::::::::::::::::::::::::::::::::::::.::::.:::::.:.:::::::::::::.:....::::.:::::::::cc::::...........:..::::::......:::::.::.:::::::::::::::::::c::cc
::::::::::::::c::::::::::::::::.:..:::::::::::::::::::::::::::::::::.:..:::::::::::.........:..:..:...::...:. .........::.:.:::::::::::::::::::cccccco
::::::::::::::::::::::::::::::::::.:.::::::::::::::::::::::::::::::.......:..:.....coOOO88888O8OO8COCOooo....:..  ...:.:::.::::::::::.::::::cccccccccc
:::::::::::::::::::::::::::::::.:.:.::::c:::::::::::::::::::::::::::..........cO888@@@@@@88@@@@888888888888OOoCc:..:....:::::::::::::::::::::::c:ccccc
::::::::.:..:::::::::::::::::::::::::::::::::::::::::::::::::::::::........o88@888@@@88@@@@@8@@@8888888888888888OOCc.......:.:::::::::::::::::::cccccc
:::::::::::::.:::::::::::::::::.::::::::::::::::::::::::::::::::........c888@@888@@8@@@8@@@@@@8@@@888888@@8@888888O8OOc......::.::::::::::::::::cccccc
:::::::::..::::::::::::::::.::::::::::::::.::::::::.::::::.::........:888888888@888@8@@@@@@@8@@@@@@@8@@@88@@@@@8888888OOCc:......::::::::::::::cc::ccc
:::::::.:::::::::::::::::::::::::::::::::::.:.:::::::.::::::.......:88888888@8@@@@@@@@@@@@@@@@@@@@@8@@@@@@8@@8@88888888OCOc:......::::c:c::::::::::ccc
:::::::::::::::::::::::::::::::::::::::::::::.:..:::::.::::.......O8888@8@8@@@@@@8@@@@@@@@@@@@@@@@@@@@@@@888@888888888888OOcoo......::::::::::::c::::c
:::::::::::::::::::::::::::::::::::::.::::::::::.::.:...:..... ..888888888@@88@@@88@@@@@@@@@@@@@@@@@@@@@@@8@88888888888888OCOCo:.....::::::::::::::::c
::::::::::::::::::::::::cc::::::::::::::::::::.:..::::.:..:....o8@8888@88@8@8@@@@8@@@@@@@@@@@@@@@@@@@@@@8@88888888888888888OOCc.:......:::::c:::::::cc
:::::::::::::::::::::::::::::::::::::::::::::::.:::::.:.......:888888@8888@8@8@@@@@8@@@@@@@@@@@@@@@@@@@@@888888888888888888OOC::.:.::...:::::::::::ccc
:::::::::::::::::::::::::::::::::::::::::::::.::.::::........o8888@8@88@88888@88888888888@@@@@@@8@8888888888888888888888888CCC:..:........:::c::c::ccc
c:::::::.::.:::::::::::::::::::::::::::::::::.:.:.::.:......C8@@@@8@88@88888888888888888888888888888888888888888888888888OCoc.:...........:::::::::::c
:::::::::::::::::::::::::::c::::.::::::::::::.::..:........:@@@@@8@@88@8888888888888888888888888888888888888888888888888OOOcoc..............::::::::cc
:::c:::::::::::::::::::::::::::::::::::::c::..::.::........8@@@@@@@@8@88888888888888888888888888888888888888888888O888888oOoCo:oc...........::::::::c:
cc::::::::::::::::::::::::::::::::::::::::::::.:.::.......o@@@@@@@@@@@@8888888888888888888888888888888888888888OOO888O8OOCCoOCoc:.:.........::::::::cc
::::::::::::::::c:::::::::c:::::.:::::::::::::.::::.::....cOO88Oc.oOOCooooCCCCOOOO8888888888888888888888888888888OOOOO88CCCOCoo::.:.........::::::::::
:::::::::::::::::::::c:::::::::::::::::::::::.:..c:.... .cCCoCCocoCOo@8@@8888888cCOOCOCc:cc::.:OOCooooOOO888888888O8OOOOCoCCcCoc.............::::ccc:c
:::::::::::::::::::::::::::::::::::::::::::::::::.......:oo..........:oC88@8oOo8OCc:C:... . .  . ..oc.:CO888888888O8OO8CCoocooc:...:.:.......::::::::c
::::::::::::::::::::::::::::::::::::::::::::::::c...........:. ..   ..::cOOOoOOOc... .       ....     ...OO8O888OOOOOCOCooooc::.......::.....:::::::cc
::::::::::::::::::::::::::::c:::::::::::::::::::c.......:CCOoo:...... : .oOO.c...   ..       .cCCOCoo..  :cOO88O8O88OCo:oc:cocc.......:.... .::::::::c
c::::::::::::::::::::::::::::::::::::::::::::coOo:......8O:... ...... cOC888.o:.   ...      ..... coCCoc:.:cCOOO88888OC:::::.:..:...:...c.....:::c:ccc
:::::::::::::::::::::::::::::::c::::::::::::::::.:cC8o..8C............8CC@@8:c.:..      .  . ..oc :..:ccOoc:oOOO888888Oo:..::....:...........::::c:c:c
:::.::::::::::::::::::::::::::::::::::::::::::::::.....o8Oco@c.cO88c.8oO@@@@o.cCCC..   .......:.:CO.:cc.ooCooCOO8888888Oc...:.:..... :......::::c:cc::
:::::::::::::::::::::::::c:::::::::::::::::::::::c....o8C88888888OCcCO8@@@@@C.COOOOoc::.:cC:.:OOOo:oo::cOCCCCOCc:cCO8O8Oc. .:.    ..........::::::::cc
::::::::::::::::c:::c:::cc:c:::::::::::c:::::.:.:.c..:88@O8oO8OOOOOCCO@@@@@@@.cCCCCOOCooO8@OOOCOOCCocoOOOOOOOO888888Oo::c:.. ..   . .......::::c::::cc
:c:::::::::::::ccccoccccc:::::::::::::::::::.:...:.8:O8888OOOOOOOcC8O@@@@@@@@8:.COOO88OCoCO8OCCCCOOOCo@8@88@8888OOOOOOOCC:. .cOC:.    .....:::::::c:c:
::::::::::::::::ccc:c:::::c:::::::::::::::::........cO88888OOOOcC8888@8@@@8@8@8:oCCCO8@OccoooooooCOoc8O88888888OOCCCCCoc:.. .   :O.    .....:::::::::c
::::::::.::::::c:oOOCcc::::::::::::::::::.::::......O88888888888888888@8@888@888OCCOC8888CcooooCCCc8OOoOO8888OOO88888OOCoco       :...::..:::..:::::cc
:::::::::::::::::oc88888ccc:::::::::::::::::::.....:888888888888888888@8@8888@888ooO8OC8888OCCCoc888888OOO888@888888OOOCo.:.     c88@@@@@@@@c:::::::cc
:::::::::::::::::::ccC8O8@@@Cc:::::::::::::::..:...:88888888888888888888888888@@88o.OO8888888888888888@@88888888888O8OOCC.c .  :CcCOo:..:cC8@@::.c::cc
:::c:::::::::c:::::::::cO8@@@@@@C:::.::::::.:......:888888888888OOCccoooooCCO888@8OO8OooOOOoCOCOCCO88888888888OOOOOOOOCCc.     ooC  ........O88:.:.:::
::::::::::::c:::::::::::::cO88@@@@8Oc::::::::ccoCOCo88888888888O: ..........:oCOOOOO888888888888@@8888888O8OOO8OOOOOOCoc:.    :CC   .oO88:..C88c::::::
::::::::::::::::::::.::::::::::oO888888888888888888888888OOC@8@@o. cOo.      ..:co88O888888@8@@8@@@8888O88OOOOOOOCCCCCo. ..  :oC  C88888@88oCO8c...::.
::::::::::::c:::::::::::.:::::..:O8CCO8888888888888OCCooc::o8@@@@8COO:co..... ...8888O8888888@@8@@@@888O8OOOOOOOCCoCoc: .   :o8o  .oCCO88O8CCOO: .::..
:c:::::::c::::::::::.:....:..:O8888888OooC8oO888.......  .CCO88O8888OC8888O8888@8@@8888888888888@@88888OOOOOOCOCCCoCo.   ...oOOCO@CoooCO888OOC......:.
c::::::::::c:o8@88@8888888888888888888888O::coC coCooO .  .CCCO8@88OC88888888888888@@88888888888888888OOOOCOOCCCooCoo.    .cCoo8@8@8OCO88O88C.......::
c:::::::cO88@@@@@@@@88@8@8888888888888888888888o..oOOoC  .oCO88@8@8OOO888O888888@88@@@@88888888888888OOOOOCCCCOooCoCCo. ..:C:cCO8@@8OO8OOO8CcO8@@@@@@@
cc:::cO8@8@@@@@@@@@@8@@@@88888888OO8888888888888@@88O...oo:c88@@8888@@@888888888O8888@@@88OO88888O888O8OOOCCCOCOCCOCCoc.:..C8@888OO88OOO8O8@8@8O8888@@
cccc88888@8@@@@@@@@@@@8@8@@888888888OcoO888888@@@@@@@@8c:... .:::cC88OCcoO8888OOO888888@@8OOOOOO888OOOOOOOCOOOOCOCoooc:...C88888888O888O@8@O88O8O8O888
cc:C888@@@@@@@@@@@@@888888888888888888CcCOO888888@@@@@@@@@O:............ ....:cCCOOO8OOO88888O8OOOOOOOOOOOOOOOOOCCoo::.. :C8888888OOCo@88888@O8OO88O88
ccc@@888@@@@@@@@@@@@@@8@@888O88888@@@@@8....::ooCOO88@@@@88OO@@88@8@8@@@88Co... ..:oOOOOOO88888OOOOOOOOOOOOOOOOCooc:. .   .COOCCCo:  888@@@@@@@@@@8OOO
oc8888@8@@88@@8@@@@@@@@@88OOOooO88@@@@@@8o.. .......::c:::::oOOO888888888888888@@8OocoOO888888O888OOOOOOOCOOOCCCoc:.                :OCc:  ..::C@@@@OO
o@88@8888888OOO:OO8888@@@@8888C.oO8@@@@@@@O.. .... ..Oc::c....   . ..:cCCOOO888@8@88OCOO888888O8OOOOOOOCCOCCCCCo::.  . .          ......  ..::.. .C@@8
@@@@@888o:cCCcc: .::::888@@8888O..C88@@@@@88:....  ..c.:oo:...:c.cc:.:coCOO888@@@@@@8888O8OOOOOOOOOOCCCoCooooc::..   ..            .:c...::::......:OO
@@@@@88OOOOo.....     .CO888@@@8O...CO8@@@88O:.... ....:cooooCCoccCCOO8@@@@@@@@@8@888OOOOOOOOOOOOOOCococcooocc:.    .::           .  ........     ..o@
@@@@@@@88OOOOC          oO8888@888.. cO8@@@888.      ..:COOOOO88888@@@@@@@@@@@@@888888OOOOOOOOCoCOCoCooc:c:cc..    ..:c..       .. ..:...          .o8
@@@@@@88OOOOCo           .oC888@88O....CO8@@@@@.    ..:::coo888888@@@@@@@@@@@@@888888OOOOOOOCCOCCCoocc:::::...  ....:coc.    ..    .:.....   .     ..:
@@@@8888OOOCCo...       .   COO88OOO....oCO888@8O..  .....::@@@8888@@@@@@@@@@888888888OOOOOOOOCoco::::... ...  ..:::oOCo:            ....    .. ...  .
@@@888OOOOOOocCC           ..oCOO8OOo..  .::coCCC.   ......cO8@8@@888888@@8@888888OOOOOOCOCCooo:::.... ....    .:::oCCC:              ...    .   ... .
@888OOOOOOOCCOCo          ... :coOO8@8...   ... .  .  .....:ccoCOOOOOOOOOOO8OOOCooCooo:cc:............ ..  ....::cCCC:. .   .         ...     cOOo....
88OOCCCOOOOCCCc:8c          ....:coOO@8..    ........ .... ....:.:coccccoccocco::.:...... ...............  ..:::cCCo...               ...  ..c@@@@O:..
8OOOOCCCCCCooooO@8         .........cc:.     .... ....  ............. ......................................::c:o:.... . .          .  ..  ...O8o:....
OOOCooooCCCCoCCO8OOCc    ...........::.   . .......     ..   ......... ...........::.......................ccc::........                 .     ....:::
OOOOCCOOCCCOOOO88CO8OO.  .........::.  .  ......... .. .............. .............:... .................:c:o:ooococ....    .          ...  ..  ..::::
88OOOOCCOOOOCOOOCC88OCo. .......:.     ......................:oO@@@@c........::::::...............::::..cocc:cOOOO88o....             .... ..     .. .
88888OCCCOOCOOCCoO88OCc:........      .. ..............:C8@8Ooc:.... CO......:.::::.............:.::::cooooCO8OO@@@O...  ..               .....   .o88
888OOOCCoCCCooocoOOOc:......:.  ...  ............::cC8@@OCC8@@@@@O   .@88:..:.:::::::::::.::::.::.cooooCCO888C8@@C.... . .        .:.  .    ....:oOOOO
8OOCocc:::::::c.cCcc.::..::..  .............cO@@@@@@@@@@@@@@@Ooc:.    888@...::::::::::::::::::ccoCooooCO88@@@@O......                .  ....:O@@OCCCC
Oooo:..........:::::::c:...............:cO@@@@@8OoooC88Ooc::::....... C8@@@c.:::::::::::::::oCoCCCCCoC88@@@@@Ooc..::cc . .         .......oO88888Oo::.
8OCo:::..:.:.:.:::::..... .....::coCOOOCCooocc:oO@@@@c.:8@@@@@o.......:@@@@8::::::cccccccCCCCCCCCCCoCOO8@@@8Coco:oO8... . .      ..  .   O@@8OCCo:::..
888OOocccccccCoc::........:O@@@@@@@@@@@@@@@@@@@@@@@8O88O::c:c:.:.::.:::O@@@@8:cccccoooCCOCOCCCCOOOOC::::cooCCooO88C............. ........OOOCoc:.:::o@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@8@O@@O@O8@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@O@@@@8@@@@@8O@
*/